/*
 * Copyright (C) 2010 Moduad Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.androidpn.client;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.AndFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketIDFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Registration;
import org.jivesoftware.smack.provider.ProviderManager;

/**
 * This class is to manage the XMPP connection between client and server.
 * 
 * @author Sehwan Noh (devnoh@gmail.com)
 */
public class XmppManager {

	private static final String XMPP_RESOURCE_NAME = "AndroidpnClient";

	private Logger logger = Logger.getLogger(XmppManager.class);

	private String xmppHost;

	private int xmppPort;

	private XMPPConnection connection;

	private String username;

	private String password;

	private ConnectionListener connectionListener;

	private PacketListener notificationPacketListener;

	private List<Runnable> taskList;

	private TaskSubmitter taskSubmitter;

	private boolean running = false;

	private Future<?> futureTask;

	private static ExecutorService executorService;

	private Thread reconnection;

	public XmppManager() {
		//xmppHost = "127.0.0.1";
		//xmppPort = 5222;
		xmppHost = "101.227.175.36";
		xmppPort = 20368;
		
		username = "21cba9fecf614d13887726e09f0b84cf";
		password = "cc64dbb17c0e46f0af58557ca31f03e9";
		
		connectionListener = new PersistentConnectionListener(this);
		notificationPacketListener = new NotificationPacketListener(this);
		
		taskList = new ArrayList<Runnable>();
		reconnection = new ReconnectionThread(this);
		executorService = Executors.newFixedThreadPool(100);
		taskSubmitter = new TaskSubmitter();
	}

	public void connect() {
		logger.debug("connect()...");
		submitConnectTask();
		submitLoginTask();
		Message msg = createMessage("id", "想大猪/qq", "大猪大猪是我啊/cy", "uri");
		
		submitPushTask(msg);
	}

	public void disconnect() {
		logger.debug("disconnect()...");
		terminatePersistentConnection();
	}

	public void pushMsg(Message msg) {
		logger.debug("pushMsg()...");
		submitPushTask(msg);
	}
	
	public void terminatePersistentConnection() {
		logger.debug("terminatePersistentConnection()...");
		Runnable runnable = new Runnable() {

			final XmppManager xmppManager = XmppManager.this;

			public void run() {
				if (xmppManager.isConnected()) {
					logger.debug("terminatePersistentConnection()... run()");
					xmppManager.getConnection().removePacketListener(
							xmppManager.getNotificationPacketListener());
					xmppManager.getConnection().disconnect();
				}
				xmppManager.runTask();
			}

		};
		addTask(runnable);
	}

	public XMPPConnection getConnection() {
		return connection;
	}

	public void setConnection(XMPPConnection connection) {
		this.connection = connection;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public ConnectionListener getConnectionListener() {
		return connectionListener;
	}

	public PacketListener getNotificationPacketListener() {
		return notificationPacketListener;
	}

	public void startReconnectionThread() {
		synchronized (reconnection) {
			if (!reconnection.isAlive()) {
				reconnection.setName("Xmpp Reconnection Thread");
				reconnection.start();
			}
		}
	}

	public void reregisterAccount() {
		removeAccount();
		submitLoginTask();
		runTask();
	}

	public List<Runnable> getTaskList() {
		return taskList;
	}

	public Future<?> getFutureTask() {
		return futureTask;
	}

	public void runTask() {
		logger.debug("runTask()...");
		synchronized (taskList) {
			running = false;
			futureTask = null;
			if (!taskList.isEmpty()) {
				Runnable runnable = (Runnable) taskList.get(0);
				taskList.remove(0);
				running = true;
				logger.debug("==================================runTask()..." + runnable.getClass());
				futureTask = taskSubmitter.submit(runnable);
			}
		}
		logger.debug("runTask()...done");
	}

	private String newRandomUUID() {
		String uuidRaw = UUID.randomUUID().toString();
		return uuidRaw.replaceAll("-", "");
	}

	private boolean isConnected() {
		return connection != null && connection.isConnected();
	}

	private boolean isAuthenticated() {
		return connection != null && connection.isConnected()
				&& connection.isAuthenticated();
	}

	private boolean isRegistered() {
		return username != null && password != null;
	}

	private void submitConnectTask() {
		logger.debug("submitConnectTask()...");
		addTask(new ConnectTask());
	}

	private void submitRegisterTask() {
		logger.debug("submitRegisterTask()...");
		submitConnectTask();
		addTask(new RegisterTask());
	}

	private void submitLoginTask() {
		logger.debug("submitLoginTask()...");
		submitRegisterTask();
		addTask(new LoginTask());
	}
	
	private void submitPushTask(Message msg) {
		logger.debug("submitPushTask()...");
		//submitLoginTask();
		addTask(new PushTask(msg));
	}

	public void addTask(Runnable runnable) {
		logger.debug("addTask(runnable)...");
		synchronized (taskList) {
			if (taskList.isEmpty() && !running) {
				running = true;
				futureTask = taskSubmitter.submit(runnable);
			} else {
				//runTask();
				taskList.add(runnable);
			}
		}
		logger.debug("addTask(runnable)" + runnable.getClass() + "... done");
		for(Runnable r : taskList) {
			System.out.print(r+" ");
		}
		System.out.println();
	}

	private void removeAccount() {
		this.setUsername(null);
		this.setPassword(null);
	}

	public void handle(Runnable runnable) {
		new Thread(runnable).start();
	}

	// ///////////////////Inner Class///////////////////////
	/**
	 * A runnable task to connect the server.
	 */
	private class ConnectTask implements Runnable {

		final XmppManager xmppManager;

		private ConnectTask() {
			this.xmppManager = XmppManager.this;
		}

		public void run() {
			logger.info("ConnectTask.run()...");

			if (!xmppManager.isConnected()) {
				// Create the configuration for this new connection
				ConnectionConfiguration connConfig = new ConnectionConfiguration(
						xmppHost, xmppPort);
				// connConfig.setSecurityMode(SecurityMode.disabled);
				connConfig.setSecurityMode(SecurityMode.required);
				connConfig.setSASLAuthenticationEnabled(false);
				connConfig.setCompressionEnabled(false);

				XMPPConnection connection = new XMPPConnection(connConfig);
				xmppManager.setConnection(connection);

				try {
					// Connect to the server
					connection.connect();
					logger.info("XMPP connected successfully");

					// packet provider
					ProviderManager.getInstance().addIQProvider("notification",
							"androidpn:iq:notification",
							new NotificationIQProvider());

				} catch (XMPPException e) {
					logger.error("XMPP connection failed", e);
				}

				xmppManager.runTask();

			} else {
				logger.info("XMPP connected already");
				xmppManager.runTask();
			}
		}
	}

	/**
	 * A runnable task to register a new user onto the server.
	 */
	private class RegisterTask implements Runnable {

		final XmppManager xmppManager;

		private RegisterTask() {
			xmppManager = XmppManager.this;
		}

		public void run() {
			logger.info("RegisterTask.run()...");

			if (!xmppManager.isRegistered()) {
				final String newUsername = newRandomUUID();
				final String newPassword = newRandomUUID();

				Registration registration = new Registration();

                PacketFilter packetFilter = new AndFilter(new PacketIDFilter(
                        registration.getPacketID()), new PacketTypeFilter(
                        IQ.class));

                PacketListener packetListener = new PacketListener() {

                    public void processPacket(Packet packet) {
                        logger.debug("RegisterTask.PacketListener processPacket().....");
                        logger.debug("RegisterTask.PacketListener packet="
                                + packet.toXML());

                        if (packet instanceof IQ) {
                            IQ response = (IQ) packet;
                            if (response.getType() == IQ.Type.ERROR) {
                                if (!response.getError().toString().contains(
                                        "409")) {
                                    logger.error(
                                            "Unknown error while registering XMPP account! "
                                                    + response.getError()
                                                            .getCondition());
                                }
                            } else if (response.getType() == IQ.Type.RESULT) {
                                xmppManager.setUsername(newUsername);
                                xmppManager.setPassword(newPassword);
                                logger.debug("Register username=" + newUsername);
                                logger.debug("Register password=" + newPassword);

                                logger.info("Account registered successfully");
                                xmppManager.runTask();
                            }
                        }
                    }
                };

                registration.setType(IQ.Type.SET);
                
				connection.addPacketListener(packetListener, packetFilter);
				
				//registration.setTo(xmppHost);
				/*Map<String, String> attributes = new HashMap<String,
				String>();
				attributes.put("username", rUsername);
				attributes.put("password", rPassword);
				registration.setAttributes(attributes);
				*/
				registration.addAttribute("username", newUsername);
				registration.addAttribute("password", newPassword);
				
				connection.sendPacket(registration);

			} else {
				logger.info("Account registered already");
				xmppManager.runTask();
			}
		}
	}

	/**
	 * A runnable task to log into the server.
	 */
	private class LoginTask implements Runnable {

		final XmppManager xmppManager;

		private LoginTask() {
			this.xmppManager = XmppManager.this;
		}

		public void run() {
			logger.info("LoginTask.run()...");

			if (!xmppManager.isAuthenticated()) {
				logger.debug("username=" + username);
				logger.debug("password=" + password);

				try {
					xmppManager.getConnection().login(
							xmppManager.getUsername(),
							xmppManager.getPassword(), XMPP_RESOURCE_NAME);
					logger.debug("Loggedn in successfully");

					// connection listener
					if (xmppManager.getConnectionListener() != null) {
						xmppManager.getConnection().addConnectionListener(
								xmppManager.getConnectionListener());
					}

					// packet filter
					PacketFilter packetFilter = new PacketTypeFilter(
							NotificationIQ.class);
					// packet listener
					PacketListener packetListener = xmppManager
							.getNotificationPacketListener();
					connection.addPacketListener(packetListener, packetFilter);

					xmppManager.runTask();

				} catch (XMPPException e) {
					logger.error("LoginTask.run()... xmpp error");
					logger.error("Failed to login to xmpp server. Caused by: "
							+ e.getMessage());
					String INVALID_CREDENTIALS_ERROR_CODE = "401";
					String errorMessage = e.getMessage();
					if (errorMessage != null
							&& errorMessage
									.contains(INVALID_CREDENTIALS_ERROR_CODE)) {
						xmppManager.reregisterAccount();
						return;
					}
					xmppManager.startReconnectionThread();

				} catch (Exception e) {
					e.printStackTrace();
					logger.error("LoginTask.run()... other error");
					logger.error("Failed to login to xmpp server. Caused by: "
							+ e.getMessage());
					xmppManager.startReconnectionThread();
				}

			} else {
				logger.info("Logged in already");
				xmppManager.runTask();
			}

		}
	}

	/**
	 * A runnable task to push message to the server.
	 */
	public class PushTask implements Runnable {

		final XmppManager xmppManager = XmppManager.this;
		final Message message;

		public PushTask() {
			message = new Message();
		}
		
		public PushTask(Message message) {
			this.message = (Message) message;
		}

		public void run() {
			logger.info("PushTask()...");
			connection.sendPacket(message);
			xmppManager.runTask();
		}
	}
	
	public class TaskSubmitter {
		@SuppressWarnings("unchecked")
		public Future submit(Runnable task) {
			Future result = null;
			if (!executorService.isTerminated()
					&& !executorService.isShutdown() && task != null) {
				result = executorService.submit(task);
			}
			return result;
		}

	}
	
	public Message createMessage(String id, String title, String message, String uri) {
		//msg.setTo("15d69e00070b41afa8289005af46535a@127.0.0.1/AndroidpnClient");
		Message msg = new Message();
		msg.setTo("broadcast@127.0.0.1/AndroidpnClient");
		msg.setType(Message.Type.chat);
		msg.setProperty("id", id);
		msg.setProperty("title", title);
		msg.setProperty("message", message);
		msg.setProperty("uri", uri);
		msg.setBody("");
		return msg;
	}

}
